// Functions associated with the PlacedObject class
//-----------------

//-----------------
// Includes libraries and other files
#include "PlacedObject.h"
//-----------------

//-----------------
//// CONSTRUCTOR - PlacedObject
PlacedObject::PlacedObject()
	: mesh_( nullptr), position_(), file_name_( ""), object_name_( ""), breakable_( false), max_health_( 1)
{}

//// CONSTRUCTOR - PlacedObject
PlacedObject::PlacedObject( const PlacedObject& o)
	: mesh_( o.mesh_), position_( o.get_x(), o.get_y(), o.get_z()), dimensions_( o.get_x_dimension(), o.get_y_dimension(), o.get_z_dimension()),
	  file_name_( o.get_file_name()), object_name_( o.get_object_name()), object_type_( o.get_object_type()),
	  breakable_( o.get_breakable()), max_health_( o.get_max_health())
{}

//// CONSTRUCTOR - PlacedObject
PlacedObject::PlacedObject( const ModelAsset& model)
	: mesh_( model.get_mesh()), position_(), dimensions_(), file_name_( model.get_file_name()), object_name_( model.get_asset_name()),
	  object_type_( model.get_asset_type()), breakable_( false), max_health_( 1)
{}

//// DESTRUCTOR - ~PlacedObject
PlacedObject::~PlacedObject() {}
//-----------------

//-----------------
//// FUNCTION - get_x
// return the object's x position
float PlacedObject::get_x() const {
	// Pre-conditions :- none
	assert( true);

	if( rotation_ == 360)
		// this option shouldn't be used, but just in case something goes wrong...
		return position_.x;
	else if( rotation_ == 270)
		return( position_.x + get_x_dimension());
	else if( rotation_ == 180)
		return( position_.x + get_x_dimension());
	else if( rotation_ == 90)
		return position_.x;
	else
		return position_.x;
}

//// FUNCTION - get_y
// return the object's y position
float PlacedObject::get_y() const {
	// Pre-conditions :- none
	assert( true);

	return position_.y;
}

//// FUNCTION - get_z
// return the object's z position
float PlacedObject::get_z() const {
	// Pre-conditions :- none
	assert( true);

	if( rotation_ == 360)
		// this option shouldn't be used, but just in case something goes wrong...
		return position_.z;
	else if( rotation_ == 270)
		return( position_.z );
	else if( rotation_ == 180)
		return( position_.z + get_z_dimension());
	else if( rotation_ == 90)
		return( position_.z + get_z_dimension());
	else
		return position_.z;
}

//// FUNCTION - get_true_x
// return the object's x position without taking the rotation into account
float PlacedObject::get_true_x() const {
	// Pre-conditions :- none
	assert( true);

	return position_.x;
}

//// FUNCTION - get_true_y
// return the object's y position without taking the rotation into account
float PlacedObject::get_true_y() const {
	// Pre-conditions :- none
	assert( true);

	return position_.y;
}

//// FUNCTION - get_true_z
// return the object's z position without taking the rotation into account
float PlacedObject::get_true_z() const {
	// Pre-conditions :- none
	assert( true);

	return position_.z;
}

//// FUNCTION - get_x_dim
// return the object's x dimension
float PlacedObject::get_x_dimension() const {
	return dimensions_.x;
}

//// FUNCTION - get_y_dim
// return the object's y dimension
float PlacedObject::get_y_dimension() const {
	return dimensions_.y;
}

//// FUNCTION - get_z_dim
// return the object's z dimension
float PlacedObject::get_z_dimension() const {
	return dimensions_.z;
}

//// FUNCTION - get_file_name
// return the file name that the object is loading the mesh of
std::string PlacedObject::get_file_name() const {
	// Pre-conditions :- none
	assert( true);

	return file_name_;
}

//// FUNCTION - get_object_name
// return the name of the object
std::string PlacedObject::get_object_name() const {
	// Pre-conditions :- none
	assert( true);

	return object_name_;
}

//// FUNCTION - get_object_type
// return the object's type (i.e. Floor, Door, Wall etc.)
int PlacedObject::get_object_type() const {
	// Pre-conditions :- none
	assert( true);

	return object_type_;
}

//// FUNCTION - get_rotation
// return the current rotation of the object
int PlacedObject::get_rotation() const {
	// Pre-conditions :- none
	assert( true);

	return rotation_;
}

//// FUNCTION - get_breakable
// return whether or not the object is breakable
bool PlacedObject::get_breakable() const {
	// Pre-conditions :- none
	assert( true);

	return breakable_;
}

//// FUNCTION - get_max_health
// return the object's maximum health
int PlacedObject::get_max_health() const {
	// Pre-conditions :- none
	assert( true);

	return max_health_;
}

//// FUNCTION - render
// output the object
void PlacedObject::render() const {
	// Pre-conditions :- none
	assert( true);

	// if no mesh exists then don't attempt to render it - DUH!
	if( mesh_ != nullptr)
		mesh_->render( get_x(), get_y(), get_z(), 1.0f, 1.0f, 1.0f, rotation_);
}

//// FUNCTION - information
// return all the information about the object in a string
std::string PlacedObject::information() const {
	// Pre-conditions :- none
	assert( true);

	std::stringstream ss;

	ss << file_name_ << " "
	   << position_.x << " "
	   << position_.y << " "
	   << position_.z << " "
	   << get_x_dimension() << " "
	   << get_y_dimension() << " "
	   << get_z_dimension() << " "
	   << get_rotation() << " "
	   << get_object_name() << " " << END << " "
	   << get_object_type() << " "
	   << get_breakable() << " "
	   << get_max_health();

	return ss.str();
}

//// FUNCTION - set_mesh
// set the object's mesh pointer
void PlacedObject::set_mesh( Mesh* m) {
	// Pre-conditions :- the mesh pointer must be valid
	assert( m != nullptr);

	mesh_ = m;
	file_name_ = m->get_file_name();
}

//// FUNCTION - set_object_name
// set the object's name
void PlacedObject::set_object_name( const std::string& name) {
	// Pre-conditions :- none
	assert( true);

	object_name_ = name;
}

//// FUNCTION - set_object_type
// set the object's type
void PlacedObject::set_object_type( const int& t) {
	// Pre-conditions :- none
	assert( true);

	object_type_ = t;
}

//// FUNCTION - set_position
// set the object's position
void PlacedObject::set_position( const float& x, const float& y, const float& z) {
	// Pre-conditions :- none
	assert( true);

	position_.x = x;
	position_.y = y;
	position_.z = z;
}

//// FUNCTION - set_dimensions
// set the object's dimensions
void PlacedObject::set_dimensions( const float& x, const float& y, const float& z) {
	// Pre-conditions :- none
	assert( true);

	dimensions_.x = x;
	dimensions_.y = y;
	dimensions_.z = z;
}

//// FUNCTION - set_rotation
// set the object's rotation
void PlacedObject::set_rotation( const int& r) {
	// Pre-conditions :- must be between 0 and 360 inclusively
	assert( r >= 0 && r <= 360);

	rotation_ = r;
}

//// FUNCTION - set_breakable
// set whether the object can be destroyed by the player or not
void PlacedObject::set_breakable( const bool& b) {
	// Pre-conditions :- none
	assert( true);

	breakable_ = b;
}

//// FUNCTION - set_max_health
// set the maximum health of the object (i.e. more health = longer before it can be destroyed)
void PlacedObject::set_max_health( const int& h) {
	// Pre-conditions :- the health must be valid
	assert( h > 0);

	max_health_ = h;
}

//// FUNCTION - rotate_clockwise
// rotate the object by 90 degrees in a clockwise direction
void PlacedObject::rotate_clockwise() {
	// Pre-conditions :- none
	assert( true);

	// if the rotation has gone full circle then reset the angle to prevent any positioning problems
	if( rotation_ == 270)
		rotation_ = 0;
	else
		rotation_ += 90;
}

//// FUNCTION - rotate_anticlockwise
// rotate the object by 90 degrees in an anti-clockwise direction
void PlacedObject::rotate_anticlockwise() {
	// Pre-conditions :- none
	assert( true);

	// if the rotation has gone full circle then reset the angle to prevent any positioning problems
	if( rotation_ == 0)
		rotation_ = 270;
	else
		rotation_ -= 90;
}

//// OVERLOADED OPERATOR - =
PlacedObject& PlacedObject::operator =( const ModelAsset& a) {
	// Pre-conditions :- none
	assert( true);

	// no need to check for self-assignment as they're two different classes
	mesh_ = a.get_mesh();
	file_name_ = a.get_file_name();
	object_name_ = a.get_asset_name();
	object_type_ = a.get_asset_type();
	set_dimensions( a.get_dimension_x(), a.get_dimension_y(), a.get_dimension_z());

	// reset all the other variables (they SHOULD be changed soon after this assignment anyway)
	set_position( 0, 0, 0);
	rotation_ = 0;
	breakable_ = false;
	max_health_ = 1;

	return (*this);
}

//// OVERLOADED OPERATOR - <<
std::ostream& operator <<( std::ostream& os, const PlacedObject& p) {
	os << p.get_file_name() << " "
	   << p.get_true_x() << " "
	   << p.get_true_y() << " "
	   << p.get_true_z() << " "
	   << p.get_x_dimension() << " "
	   << p.get_y_dimension() << " "
	   << p.get_z_dimension() << " "
	   << p.get_rotation() << " "
	   << p.get_object_name() << " " << END << " "
	   << p.get_object_type() << " "
	   << p.get_breakable() << " "
	   << p.get_max_health();

	return os;
}